package com.vois.jack.btmgr.blebase;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelUuid;
import android.text.TextUtils;
import android.util.Log;

import com.vois.jack.btmgr.classicbase.BtRecorderInterface;
import com.vois.jack.btmgr.common.DeviceRecorderControlInterface;
import com.vois.jack.btmgr.util.Logger;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public abstract class BleDevice implements BleDeviceFsm.BleDeviceFsmCallback, DeviceRecorderControlInterface {
    private Logger logger = Logger.getLogger(BleDevice.class);
    public static final int DEFAULT_MTU_SIZE = 23;

    String mac;
    String name;
    BluetoothDevice bluetoothDevice;
    boolean autoConnect;
    int scanTimeout;
    int scanTryTimes;
    int connectingTimeout;
    int connectTryTimes;
    int fullConnectTryTimes;
    BleScanRecord scanRecord;
    boolean isNeedVerified;
    Map<UUID, BluetoothGattService> gattServiceMap;
    BleDeviceFsm bleDeviceFsm;
    BleDeviceListener listener;
    BleDeviceCloseListener closeListener;
    boolean shouldStartWithScan;
    int currentMTU = 23;
    int currentRssi;
    boolean fullReConnectProcessFirst;
    boolean isOpened;

    public interface BleDeviceCloseListener {
        void onBleDeviceClosed(BleDevice bleDevice);
    }

    class SendDataProcess implements BleAction.BleActionCallback {
        private byte[] data;
        private int dataLen;
        private int sentLen;
        private UUID serviceUUID;
        private UUID characteristicUUID;
        private BleAction.BleActionCallback sendDataCallback;

        SendDataProcess(byte[] data, int dataLen, UUID serviceUUID, UUID characteristicUUID, BleAction.BleActionCallback sendDataCallback) {
            this.data = data;
            this.dataLen = dataLen;
            this.serviceUUID = serviceUUID;
            this.characteristicUUID = characteristicUUID;
            this.sendDataCallback = sendDataCallback;
            sentLen = 0;
        }

        void executeSending() {
            byte[] writeData;
            int writeLen = 0;
            int leftLen = dataLen - sentLen;

            logger.d( "executeSending: dataLen:" + dataLen + " sentLen:" + sentLen);
            logger.d( "executeSending: mtu:" + getCurrentMTU());

            if (leftLen < getCurrentMTU()) {
                writeLen = leftLen;
            }else {
                writeLen = getCurrentMTU();
            }

            writeData = new byte[writeLen];
            System.arraycopy(data, sentLen, writeData, 0, writeLen);
            Bundle bundle = new Bundle();
            bundle.putParcelable(BleDeviceFsm.EXTRA_SERVICE_UUID, new ParcelUuid(serviceUUID));
            bundle.putParcelable(BleDeviceFsm.EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristicUUID));
            bundle.putByteArray(BleDeviceFsm.EXTRA_RAW_DATA, writeData);
            BleAction bleAction = new BleAction();
            bleAction.setCallback(this);
            bleAction.setCmdArg(bundle);
            bleAction.setCmd(BleAction.BleCmd.BLE_CMD_WRITE_DATA);
            getBleDeviceFsm().executeBleAction(bleAction);
        }

        @Override
        public void onActionResult(int status, Bundle result) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                byte[] data = result.getByteArray(BleDeviceFsm.EXTRA_VALUE_DATA);

                if (data != null) {
                    sentLen += data.length;
                }

                logger.d( "onActionResult: dataLen:" + dataLen + " sentLen:" + sentLen);
                if (sentLen >= dataLen) {
                    if (sendDataCallback != null) {
                        sendDataCallback.onActionResult(BluetoothGatt.GATT_SUCCESS, null);
                    }
                }else {
                    executeSending();
                }
            }else {
                if (sendDataCallback != null) {
                    sendDataCallback.onActionResult(BluetoothGatt.GATT_FAILURE, null);
                }
            }
        }
    }

    public interface BleDeviceListener {
        void onConnecting(BluetoothDevice device);
        void onConnected(BluetoothDevice device);
        void onDisconnected(BluetoothDevice device);
        void onMessage(BluetoothDevice device, Message msg);
        void onClosed(BluetoothDevice device);
        void onError(BluetoothDevice device, int errorCause);
        void onReady(BluetoothDevice device);
        void onRssi(BluetoothDevice device, int rssi);
    }

    public BleDevice() {
        mac = null;
        autoConnect = true;
        scanTimeout = 60000; // 10 secs
        scanTryTimes = 2;
        connectingTimeout = 30000; // 30 secs
        connectTryTimes = 2;
        fullReConnectProcessFirst = false;
    }

    public void setCloseListener(BleDeviceCloseListener closeListener) {
        this.closeListener = closeListener;
    }

    void init(BluetoothDevice device, String name) {
        this.name = name;
        mac = device.getAddress();
        bluetoothDevice = device;
        openBleDevice(false);
    }

    public boolean isFullReConnectProcessFirst() {
        return fullReConnectProcessFirst;
    }

    public void setFullReConnectProcessFirst(boolean fullReConnectProcessFirst) {
        this.fullReConnectProcessFirst = fullReConnectProcessFirst;
    }

    public void setListener(BleDeviceListener listener) {
        this.listener = listener;
    }

    public BleDeviceListener getListener() {
        return listener;
    }

    public String getMac() {
        return mac;
    }

    //获取协议内蓝牙地址
    public void getAddress(){
        if (listener != null) {
            final String address = (TextUtils.isEmpty(mac) ? "" : mac.replaceAll(":", "").toLowerCase());
            Message message = Message.obtain();
            message.what = BleDevCommonMsg.BLE_DEV_COMMON_GET_ADDRESS_RESULT.getValue();
            Bundle bundle = new Bundle();
            bundle.putString(BleConstant.EXTRA_ADDRESS, address);
            message.setData(bundle);
            listener.onMessage(getBluetoothDevice(), message);
        }
    }

    public boolean isAutoConnect() {
        return autoConnect;
    }

    public int getScanTimeout() {
        return scanTimeout;
    }

    public int getScanTryTimes() {
        return scanTryTimes;
    }

    public int getConnectingTimeout() {
        return connectingTimeout;
    }

    public int getConnectTryTimes() {
        return connectTryTimes;
    }

    public int getFullConnectTryTimes() {
        return fullConnectTryTimes;
    }

    public boolean isNeedVerified() {
        return isNeedVerified;
    }

    public void setNeedVerified(boolean needVerified) {
        isNeedVerified = needVerified;
    }

    public BluetoothDevice getBluetoothDevice() {
        return bluetoothDevice;
    }

    public BleScanRecord getScanRecord() {
        return scanRecord;
    }

    protected BleDeviceFsm getBleDeviceFsm() {
        return bleDeviceFsm;
    }

    public void setBluetoothDevice(BluetoothDevice bluetoothDevice) {
        this.bluetoothDevice = bluetoothDevice;
    }

    public int getCurrentMTU() {
        return currentMTU;
    }

    public void setCurrentMTU(int currentMTU) {
        this.currentMTU = currentMTU;
    }

    public int getCurrentRssi() {
        return currentRssi;
    }

    public void setCurrentRssi(int currentRssi) {
        this.currentRssi = currentRssi;
        if (getListener() != null) {
            getListener().onRssi(getBluetoothDevice(), currentRssi);
        }
    }

    public void setScanTimeout(int scanTimeout) {
        this.scanTimeout = scanTimeout;
    }

    public void setScanTryTimes(int scanTryTimes) {
        this.scanTryTimes = scanTryTimes;
    }

    public void setConnectingTimeout(int connectingTimeout) {
        this.connectingTimeout = connectingTimeout;
    }

    public void setConnectTryTimes(int connectTryTimes) {
        this.connectTryTimes = connectTryTimes;
    }

    public void setFullConnectTryTimes(int fullConnectTryTimes) {
        this.fullConnectTryTimes = fullConnectTryTimes;
    }

    public void setScanRecord(byte[] scanData) {
        if(scanData != null) {
            scanRecord = BleScanRecord.parseFromBytes(scanData);
        }
    }

    abstract public void getValidationData();

    public BluetoothGattCharacteristic getCharacteristic(UUID serviceUUID, UUID characteristicUUID) {
        BluetoothGattService bluetoothGattService = getService(serviceUUID);

        if (bluetoothGattService != null) {
            return bluetoothGattService.getCharacteristic(characteristicUUID);
        }

        return null;
    }

    public BluetoothGattService getService(UUID serviceUUID) {
        if (gattServiceMap != null) {
            return gattServiceMap.get(serviceUUID);
        }

        return null;
    }

    public BluetoothGattDescriptor getDescriptor(UUID serviceUUID, UUID characteristicUUID, UUID descriptorUUID) {
        BluetoothGattCharacteristic characteristic = getCharacteristic(serviceUUID, characteristicUUID);
        if (characteristic != null) {
            return characteristic.getDescriptor(descriptorUUID);
        }

        return null;
    }

    public boolean connectBleDevice() {
        if (bleDeviceFsm != null && bleDeviceFsm.isWorking()) {
            bleDeviceFsm.connectDevice();
            return true;
        }

        return false;
    }

    public boolean disconnectBleDevice() {
        if (bleDeviceFsm != null && bleDeviceFsm.isWorking()) {
            bleDeviceFsm.disconnectDevice();
            return true;
        }

        return false;
    }

    public boolean openBleDevice(boolean shouldStartWithScan) {
        logger.d("openBleDevice: " + getName() + ":" + shouldStartWithScan);
        if (bleDeviceFsm != null) {
            bleDeviceFsm.stopWorking();
            bleDeviceFsm.detachBleDevice();
        }

        this.shouldStartWithScan = shouldStartWithScan;
        bleDeviceFsm = new BleDeviceFsm(name, this);
        bleDeviceFsm.start();
        isOpened = true;
        return true;
    }

    public boolean closeBleDevice() {
        if (bleDeviceFsm != null) {
            bleDeviceFsm.stopWorking();
            isOpened = false;
            return true;
        }

        return false;
    }

    public void sendWriteAction(UUID serviceUUID, UUID characteristicUUID, byte[] data, int len, BleAction.BleActionCallback callback) {
        SendDataProcess sendDataProcess = new SendDataProcess(data, len, serviceUUID, characteristicUUID, callback);
        sendDataProcess.executeSending();
    }

    public void writeCharacteristicReliable(UUID serviceUUID, UUID characteristicUUID, byte[] data, int len, BleAction.BleActionCallback callback) {
        logger.d("writeCharacteristicReliable len:" + len);
        if (len <= getCurrentMTU()) {
            Bundle bundle = new Bundle();
            bundle.putParcelable(BleDeviceFsm.EXTRA_SERVICE_UUID, new ParcelUuid(serviceUUID));
            bundle.putParcelable(BleDeviceFsm.EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristicUUID));
            bundle.putByteArray(BleDeviceFsm.EXTRA_RAW_DATA, data);
            BleAction bleAction = new BleAction();
            bleAction.setCallback(callback);
            bleAction.setCmdArg(bundle);
            bleAction.setCmd(BleAction.BleCmd.BLE_CMD_WRITE_RELIABLE);
            getBleDeviceFsm().executeBleAction(bleAction);
        }
    }

    public void writeCharacteristicData(UUID serviceUUID, UUID characteristicUUID, byte[] data, int len, BleAction.BleActionCallback callback) {
        if (len < getCurrentMTU()) {
            Bundle bundle = new Bundle();
            bundle.putParcelable(BleDeviceFsm.EXTRA_SERVICE_UUID, new ParcelUuid(serviceUUID));
            bundle.putParcelable(BleDeviceFsm.EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristicUUID));
            bundle.putByteArray(BleDeviceFsm.EXTRA_RAW_DATA, data);
            BleAction bleAction = new BleAction();
            bleAction.setCallback(callback);
            bleAction.setCmdArg(bundle);
            bleAction.setCmd(BleAction.BleCmd.BLE_CMD_WRITE_DATA);
            getBleDeviceFsm().executeBleAction(bleAction);
        }
    }

    @Override
    public void onServiceDiscoveryState(int state) {
        if (state == BleDeviceFsm.STATE_FOUND) {
            BluetoothGatt bluetoothGatt = bleDeviceFsm.getBluetoothGatt();

            if (bluetoothGatt != null) {
                List<BluetoothGattService> bluetoothGattServiceList = bluetoothGatt.getServices();
                for (BluetoothGattService service: bluetoothGattServiceList) {
                    if (gattServiceMap == null) {
                        gattServiceMap = new HashMap<>();
                    }

                    gattServiceMap.put(service.getUuid(), service);
                }
            }
        }
    }

    @Override
    public void onInited() {
        bleDeviceFsm.startWorking(shouldStartWithScan);
    }

    @Override
    public void onConnectStateChanged(int state, int reason) {
        if (state == BluetoothProfile.STATE_CONNECTING) {
            if (listener != null) {
                listener.onConnecting(getBluetoothDevice());
            }
        }else if (state == BluetoothProfile.STATE_DISCONNECTED) {
            if (listener != null) {
                listener.onDisconnected(getBluetoothDevice());
            }
        }else if (state == BluetoothProfile.STATE_CONNECTED) {
            if (listener != null) {
                listener.onConnected(getBluetoothDevice());
            }
        }
    }

    @Override
    public void onReady() {
        if (listener != null) {
            listener.onReady(getBluetoothDevice());
        }
    }

    @Override
    public void onFsmStopped() {
        logger.d("onFsmStopped");
        if (listener != null) {
            listener.onClosed(getBluetoothDevice());
        }

        if (closeListener != null) {
            closeListener.onBleDeviceClosed(this);
        }
    }

    public void requestRssi(BleAction.BleActionCallback callback) {
        BleAction bleAction = new BleAction();
        bleAction.callback = callback;
        bleAction.cmd = BleAction.BleCmd.BLE_CMD_READ_RSSI;
        bleAction.cmdArg = null;
        getBleDeviceFsm().executeBleAction(bleAction);
    }

    public void negotiateMTU(int mtu, BleAction.BleActionCallback callback) {
        BleAction bleAction = new BleAction();
        bleAction.callback = callback;
        bleAction.cmd = BleAction.BleCmd.BLE_CMD_REQUEST_MTU;
        bleAction.cmdArg = new Bundle();
        bleAction.cmdArg.putInt(BleDeviceFsm.EXTRA_MTU_VALUE, mtu);
        getBleDeviceFsm().executeBleAction(bleAction);
    }

    public boolean isConnected() {
        if (bleDeviceFsm != null) {
            return bleDeviceFsm.isConnected();
        }

        return false;
    }

    @Override
    public void onError(int errorCause) {
        if (getListener() != null) {
            getListener().onError(getBluetoothDevice(), errorCause);
        }
    }

    public void startRequestRssi(int delay) {
        if (bleDeviceFsm != null) {
            bleDeviceFsm.requestRssiFrequently(delay);
        }
    }

    public void stopRequestRssi() {
        if (bleDeviceFsm != null) {
            bleDeviceFsm.stopRequestRssi();
        }
    }

    public boolean isWorking() {
        if (bleDeviceFsm != null) {
            return bleDeviceFsm.isWorking();
        }
        return false;
    }

    public abstract void setSeqID(String seqID);

    //设备类标注
    public abstract String getDeviceModel();

    //蓝牙协议返回 -- 有些厂家可能没写
    public abstract void getVendor();

    //蓝牙协议返回
    public abstract void getVersion();

    //协议名称
    public abstract String getProtocolName();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean hasRecorder() {
        return false;
    }

    @Override
    public int getSampleRate() {
        return 0;
    }

    @Override
    public boolean startRecorder(ResultCallback callback) {
        return false;
    }

    @Override
    public void stopRecorder(ResultCallback callback) {

    }

    @Override
    public int getDefaultRecorderType() {
        return 0;
    }

    @Override
    public BtRecorderInterface getRecorder(int recoderType) {
        return null;
    }
}
